﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.ComponentModel;
using System.Reflection;
using System.Collections;
using ProgNetComponents.Classes;
using System.Drawing;
using ProgNetComponents.Utils;

namespace System
{

    /// <summary>
    /// Extension methods for object type
    /// </summary>
    public static class ObjectExt
    {
        #region Initialization
        /// <summary>
        /// NumericTypes
        /// </summary>
        private static Type[] NumericTypes = new Type[]
        {
            typeof(sbyte),
            typeof(byte),
            typeof(short),
            typeof(ushort),
            typeof(int),
            typeof(uint),
            typeof(long),
            typeof(ulong),
            typeof(float),
            typeof(double),
            typeof(decimal)
        };

        /// <summary>
        /// ProgNetInitialized
        /// </summary>
        private static bool ProgNetInitialized = false;
        /// <summary>
        /// Sets ProgNetInitialized to true
        /// Should be called once when Application starts
        /// </summary>
        /// <param name="obj">The object.</param>
        public static void SetProgNetInitialized(this object obj)
        {
            ProgNetInitialized = true;
        }

        /// <summary>
        /// Specifies if ProgNetInitialized is set.
        /// ProgNetInitialized can be set by calling System.ObjectExt.SetProgNetInitialized(null) or "".SetProgNetInitialized()
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <returns></returns>
        public static bool IsProgNetInitialized(this object obj)
        {
            return ProgNetInitialized;
        }
        #endregion

        /// <summary>
        /// Check if objects are equal. Null safe
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static bool EqualsSafe(this object obj, object value)
        {
            bool result = false;
            try
            {
                result = value == obj;
            }
            catch { }
            if (!result)
            {
                try
                {
                    result = obj.IsTheSame(value);
                }
                catch { }
            }
            return result;
        }

        #region IsNull IsNotNull
        /// <summary>
        /// Check if given object is null, BDNull, empty string or empty array
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="DBNull">If false - only for null value true is returned</param>
        /// <returns></returns>
        public static bool IsNull(this object value, bool DBNull = true)
        {
            if (value == null)
                return true;
            if (DBNull && value == System.DBNull.Value)
                return true;
            if (value.ToString().Trim() == string.Empty)
                return true;
            if (value is Array)
                return (value as Array).Length == 0;
            return false;
        }

        /// <summary>
        /// Check if value is different than null, empty string, empty array and DBNull if parameter DBNull is true
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="DBNull">if set to <c>true</c> [database null].</param>
        /// <returns></returns>
        public static bool IsNotNull(this object value, bool DBNull = true)
        {
            return !IsNull(value, DBNull);
        }
       
        #endregion

        #region Conversion to other types

        /// <summary>
        /// Returns value converted to Color.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static Color AsColor(this object value)
        {
            return value.AsColor(Color.Empty);
        }
        /// <summary>
        /// Returns value converted to color.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns></returns>
        public static Color AsColor(this object value, Color defaultValue)
        {
            if (value is Color)
                return (Color)value;

            if (value == null)
                return defaultValue;
            else if (value is string)
            {
                string str = value.AsString();
                string name = str.GetFirstMatch("Color \\[(.*)\\]");
                string a = str.GetFirstMatch("A=([0-9]*)");
                string r = str.GetFirstMatch("R=([0-9]*)");
                string g = str.GetFirstMatch("G=([0-9]*)");
                string b = str.GetFirstMatch("B=([0-9]*)");
                if (a.IsNotNull())
                    return Color.FromArgb(a.AsInt(), r.AsInt(), g.AsInt(), b.AsInt());
                else
                    return Color.FromName(name);
            }
            else if (value is int)
            {
                return Color.FromArgb(value.AsInt());
            }
            else if (value is int[])
            {
                int[] tab = value as int[];
                if (tab.Length == 4)
                {
                    return Color.FromArgb(tab[0], tab[1], tab[2], tab[3]);
                }
                else if (tab.Length == 3)
                {
                    return Color.FromArgb(tab[0], tab[1], tab[2]);
                }
            }
            return defaultValue;
        }

        /// <summary>
        /// Returns value converted to string.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static string AsString(this object value)
        {
            if (value.IsNull())
                return string.Empty;
            if (value is string)
                return (string)value;
            if(value is IEnumerable)
                return (value as IEnumerable).AsString();
            return Convert.ToString(value);
        }

        /// <summary>
        /// Returns value converted to other type.
        /// </summary>
        /// <typeparam name="T">Other type</typeparam>
        /// <param name="value">The value.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <param name="throwError">if set to <c>true</c> [throw error].</param>
        /// <returns></returns>
        /// <exception cref="System.Exception">Unable to convert  + value.AsString() +  to  + typeof(T).Name</exception>
        private static T AsOther<T>(this object value, T defaultValue, bool throwError = false)
        {
            if (value != null && value != DBNull.Value)
            {
                if (value is T)
                    return (T)value;
                if (value is string)
                {
                    if(NumericTypes.Contains(typeof(T)))
                        value = value.AsString().XReplace("[.,]", System.Globalization.CultureInfo.CurrentUICulture.NumberFormat.NumberDecimalSeparator);
                }
                T result = default(T);
                MethodInfo mi = typeof(T).GetSingleMethod("TryParse", BindingFlags.Static | BindingFlags.Public);
                if (mi != null)
                {
                    bool ok = false;
                    try
                    {
                        object[] parameters = new object[]{ value, null };
                        object o = mi.Invoke(null, parameters);
                        if (o != null && o.AsBool() && parameters[1] is T)
                        {
                            ok = true;
                            return (T)parameters[1];
                        }
                    }
                    catch
                    {
                        if (!(value is string))
                        {
                            object[] parameters = new object[] { value.AsString(), null };
                            object o = mi.Invoke(null, parameters);
                            if (o != null && o.AsBool() && parameters[1] is T)
                            {
                                ok = true;
                                return (T)parameters[1];
                            }
                        }
                    }
                    if (ok)
                        return result;
                }
            }
            if (throwError)
                throw new Exception("Unable to convert " + value.AsString() + " to " + typeof(T).Name);
            return defaultValue;
        }

        /// <summary>
        /// Returns value converted to int
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="defValue">The definition value.</param>
        /// <param name="throwError">if set to <c>true</c> [throw error].</param>
        /// <returns></returns>
        public static int AsInt(this object value, int defValue = 0, bool throwError = false)
        {
            return value.AsOther<int>(defValue, throwError);
        }

        /// <summary>
        /// Returns value converted to long
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="defValue">The definition value.</param>
        /// <param name="throwError">if set to <c>true</c> [throw error].</param>
        /// <returns></returns>
        public static long AsLong(this object value, long defValue = (long)0, bool throwError = false)
        {
            return value.AsOther<long>(defValue, throwError);
        }

        /// <summary>
        /// Returns value converted to decimal
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="defValue">The definition value.</param>
        /// <param name="throwError">if set to <c>true</c> [throw error].</param>
        /// <returns></returns>
        public static decimal AsDecimal(this object value, decimal defValue = (decimal)0, bool throwError = false)
        {
            return value.AsOther<decimal>(defValue, throwError);
        }

        /// <summary>
        /// Returns value converted to double
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="defValue">The definition value.</param>
        /// <param name="throwError">if set to <c>true</c> [throw error].</param>
        /// <returns></returns>
        public static double AsDouble(this object value, double defValue = (double)0, bool throwError = false)
        {
            return value.AsOther<double>(defValue, throwError);
        }

        /// <summary>
        /// Returns value converted to float
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="defValue">The definition value.</param>
        /// <param name="throwError">if set to <c>true</c> [throw error].</param>
        /// <returns></returns>
        public static float AsFloat(this object value, float defValue = (float)0, bool throwError = false)
        {
            return value.AsOther<float>(defValue, throwError);
        }

        /// <summary>
        /// Returns value converted to bool
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="trueValues">The true values.</param>
        /// <param name="falseValues">The false values.</param>
        /// <param name="defaultValue">if set to <c>true</c> [default value].</param>
        /// <param name="throwError">if set to <c>true</c> [throw error].</param>
        /// <returns></returns>
        /// <exception cref="System.ArgumentException">Can not convert ' + str.AsString() + ' to bool.</exception>
        public static bool AsBool(this object value, BoolStateObject[] trueValues, BoolStateObject[] falseValues, bool defaultValue = false, bool throwError = false)
        {
            if(value is bool)
                return (bool)value;
            if (value is DefaultBool)
            {
                DefaultBool d = value.AsEnum<DefaultBool>(DefaultBool.Default);
                if (d == DefaultBool.Default)
                    return defaultValue;
                if (d == DefaultBool.True)
                    return true;
                return false;
            }
            bool result = defaultValue;
            
            try
            {
                if (value is bool)
                {
                    result = (bool)value;
                    return result;
                }
            }
            catch { }
            
            string str = value.AsString();
            string strUpper= str.ToUpper();
            string tmpStr;
            string valStr;
            if (bool.TryParse(str, out result))
                return result;
            else
                result = defaultValue;

            if (trueValues != null)
            {
                foreach (BoolStateObject o in trueValues)
                {
                    if (o.Value.EqualsSafe(value))
                        return true;
                    if (o == value)
                        return true;
                    if (o.ConvertToString)
                    {
                        if (o.IgnoreCase)
                        {
                            valStr = o.Value.AsString().ToUpper();
                            tmpStr = strUpper;
                        }
                        else
                        {
                            valStr = o.Value.AsString();
                            tmpStr = str;
                        }
                        if (tmpStr == valStr)
                            return true;
                    }
                }
            }

            if (falseValues != null)
            {
                foreach (BoolStateObject o in falseValues)
                {
                    if (o.Value.EqualsSafe(value))
                        return false;
                    if (o == value)
                        return false;
                    if (o.ConvertToString)
                    {
                        if (o.IgnoreCase)
                        {
                            valStr = o.Value.AsString().ToUpper();
                            tmpStr = strUpper;
                        }
                        else
                        {
                            valStr = o.Value.AsString();
                            tmpStr = str;
                        }
                        if (tmpStr == valStr)
                            return false;
                    }
                }
            }
            if (throwError)
                throw new ArgumentException("Can not convert '" + str.AsString() + "' to bool.");

            return result;
        }

        /// <summary>
        /// Converts object to bool using System.BoolStateObject.StandardTrueValues and System.BoolStateObject.StandardFalseValues
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="defaultValue">if set to <c>true</c> [default value].</param>
        /// <param name="throwError">if set to <c>true</c> [throw error].</param>
        /// <returns></returns>
        public static bool AsBool(this object value, bool defaultValue = false, bool throwError = false)
        {
            return value.AsBool(BoolStateObject.StandardTrueValues, BoolStateObject.StandardFalseValues, defaultValue, throwError);
        }

        /// <summary>
        /// Converts value to DateTime
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="defValue">The definition value.</param>
        /// <param name="throwError">if set to <c>true</c> [throw error].</param>
        /// <returns></returns>
        public static DateTime AsDateTime(this object value, DateTime defValue , bool throwError = false)
        {
            return value.AsOther<DateTime>(defValue, throwError);
        }

        /// <summary>
        /// Converts value to enum
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value">The value.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <param name="throwError">if set to <c>true</c> [throw error].</param>
        /// <returns></returns>
        /// <exception cref="System.ArgumentException">Cannot convert ' + value.AsString() + ' to  + typeof(T).FullName</exception>
        public static T AsEnum<T>(this object value, T defaultValue, bool throwError = false) where T : struct
        {
            T result = defaultValue;
            try
            {
                if (value is T)
                {
                    result = (T)value;
                    return result;
                }
            }
            catch { }

            if (!Enum.TryParse<T>(value.AsString(), out result))
            {
                if (throwError)
                    throw new ArgumentException("Cannot convert '" + value.AsString() + "' to " + typeof(T).FullName);
                else
                    result = defaultValue;
            }
            return result;
        }

        /// <summary>
        /// Changes the type.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="conversionType">Type of the conversion.</param>
        /// <returns></returns>
        /// <exception cref="System.ArgumentNullException">conversionType</exception>
        public static object ChangeType(this object value, Type conversionType)
        {
            if (conversionType == null)
            {
                throw new ArgumentNullException("conversionType");
            }
            if (conversionType.IsGenericType &&
                conversionType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
            {
                if (value == null)
                {
                    return null;
                }
                NullableConverter nullableConverter = new NullableConverter(conversionType);
                conversionType = nullableConverter.UnderlyingType;
            }
            try
            {
                value = Convert.ChangeType(value, conversionType);
            }
            catch (Exception ex)
            {
                ex.LogInternal();
                try
                {
                    TypeConverter converter = TypeDescriptor.GetConverter(conversionType);
                    if (converter != null)
                    {
                        value = converter.ConvertFrom(value);
                    }
                }
                catch (Exception ex2)
                {
                    ex2.LogInternal();
                }
            }
            return value;
        }

        /// <summary>
        /// Returns value converted to T.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="e">The e.</param>
        /// <param name="value">The value.</param>
        /// <param name="throwError">if set to <c>true</c> [throw error].</param>
        /// <returns></returns>
        public static T FromObject<T>(this T e, object value, bool throwError = false) where T : struct
        {
            return value.AsEnum<T>(e, throwError);
        }
        /// <summary>
        /// Returns value converted to string.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns></returns>
        public static string AsString(this Keys key)
        {
            string str = "";
            if (key == Keys.None)
            {
                return str;
            }
            else
            {
                if ((key & Keys.Control) != Keys.None || key == (Keys.LButton | Keys.ShiftKey))
                    str = "CTRL";
                if ((key & Keys.Shift) != Keys.None || key == Keys.ShiftKey)
                    str = str + (str.Length > 0 ? "+" : "") +"SHIFT";
                if ((key & Keys.Alt) != Keys.None || key == Keys.Alt)
                    str = str + (str.Length > 0 ? "+" : "") + "ALT";
                key &= Keys.KeyCode;
                if (key != Keys.None)
                    str = str + (str.Length > 0 ? "+" : "") + ((object)key).ToString();
                return str;
            }
        }
        #endregion

        #region reflection
        /// <summary>
        /// Methods
        /// </summary>
        private static Dictionary<Type, Dictionary<string, MethodInfo>> Methods = new Dictionary<Type, Dictionary<string, MethodInfo>>();
        /// <summary>
        /// Properties
        /// </summary>
        private static Dictionary<Type, Dictionary<string, PropertyInfo>> Properties = new Dictionary<Type, Dictionary<string, PropertyInfo>>();
        /// <summary>
        /// Fields
        /// </summary>
        private static Dictionary<Type, Dictionary<string, FieldInfo>> Fields = new Dictionary<Type, Dictionary<string, FieldInfo>>();

        /// <summary>
        /// Gets the single method.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="name">The name.</param>
        /// <param name="flags">The flags.</param>
        /// <param name="parameters">The parameters.</param>
        /// <returns></returns>
        public static MethodInfo GetSingleMethod(this Type type, string name, BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic  | BindingFlags.FlattenHierarchy, params object[] parameters)
        {
            MethodInfo pi = null;
            
            if (type != null && name !=null)
            {
                if (Methods.ContainsKey(type) && Methods[type].ContainsKey(name))
                    return Methods[type][name];
                MethodInfo[] pis = type.GetMethods(flags);
                Type pt = null;
                bool nullable = true;
                try
                {
                    bool ignoreCase = ((flags & BindingFlags.IgnoreCase) == BindingFlags.IgnoreCase);
                    foreach (MethodInfo p in pis)
                    {
                        if ((p.Name == name) || (ignoreCase && (p.Name.ToUpper() == name.ToUpper())))
                        {
                            if (p.DeclaringType.Equals(type))
                            {
                                bool isThis = true;
                                if (parameters != null)
                                {
                                    ParameterInfo[] par = p.GetParameters();
                                    if (par.Length == parameters.Length)
                                    {
                                        for (int i = 0; i < par.Length; i++)
                                        {
                                            pt = par[i].ParameterType;
                                            nullable = false;
                                            if (!pt.IsValueType)
                                                nullable = true; // ref-type
                                            if (Nullable.GetUnderlyingType(type) != null)
                                                nullable = true; // Nullable<T>
                                            if (parameters[i] == null)
                                            {
                                                if (!nullable)
                                                {
                                                    isThis = false;
                                                    break;
                                                }
                                            }
                                            else
                                            {
                                                if (!pt.IsAssignableFrom(parameters[i].GetType()))
                                                {
                                                    isThis = false;
                                                    break;
                                                }
                                            }
                                        }
                                    }
                                }
                                if (isThis)
                                {
                                    pi = p;
                                    break;
                                }
                            }
                        }
                    }
                    if (pi == null)
                    {
                        Type t = type;

                        while (t != null)
                        {
                            t = t.BaseType;
                            pi = GetSingleMethod(t, name, flags, parameters);
                            if (pi != null)
                                break;
                        }
                    }
                }
                catch (Exception ex)
                {
                    ex.LogInternal();
                }

                if (pi != null)
                {
                    if (!Methods.ContainsKey(type))
                        Methods.Add(type, new Dictionary<string, MethodInfo>());
                    Methods[type][name] = pi;
                }
            }
            
            return pi;
        }
        /// <summary>
        /// Gets the single method.
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <param name="name">The name.</param>
        /// <param name="flags">The flags.</param>
        /// <param name="parameters">The parameters.</param>
        /// <returns></returns>
        public static MethodInfo GetSingleMethod(this object obj, string name, BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic  | BindingFlags.FlattenHierarchy, params object[] parameters)
        {
            if (obj != null)
                return obj.GetType().GetSingleMethod(name, flags, parameters);
            return null;
        }

        /// <summary>
        /// Runs the method.
        /// </summary>
        /// <param name="target">The target.</param>
        /// <param name="MethodName">Name of the method.</param>
        /// <param name="parameters">The parameters.</param>
        /// <returns></returns>
        public static object RunMethod(this object target, string MethodName, params object[] parameters)
        {
            object result = null;
            TryRunMethod(target, MethodName, out result, parameters);
            return result;
        }
        /// <summary>
        /// Tries to run method.
        /// </summary>
        /// <param name="target">The target.</param>
        /// <param name="MethodName">Name of the method.</param>
        /// <param name="result">The result.</param>
        /// <param name="parameters">The parameters.</param>
        /// <returns></returns>
        public static bool TryRunMethod(this object target, string MethodName, out object result, params object[] parameters)
        {
            bool res = false;
            result = null;
            try
            {
                MethodInfo mi = target.GetType().GetSingleMethod(MethodName, parameters: parameters);
                if (mi != null)
                {
                    result = mi.Invoke(target, parameters);
                    res = true;
                }
            }
            catch { }
            return res;
        }

        /// <summary>
        /// Gets the single property.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="name">The name.</param>
        /// <param name="flags">The flags.</param>
        /// <returns></returns>
        public static PropertyInfo GetSingleProperty(this Type type, string name, BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy)
        {
            PropertyInfo pi = null;

            if (type != null && name!=null)
            {
                if (Properties.ContainsKey(type) && Properties[type].ContainsKey(name))
                    return Properties[type][name];

                PropertyInfo[] pis = type.GetProperties(flags);
                try
                {
                    bool ignoreCase = ((flags & BindingFlags.IgnoreCase) == BindingFlags.IgnoreCase);
                    foreach (PropertyInfo p in pis)
                    {
                        if ((p.Name == name) || (ignoreCase && (p.Name.ToUpper() == name.ToUpper())))
                        {
                            if (p.DeclaringType.Equals(type))
                            {
                                pi = p;
                                break;
                            }
                        }
                    }
                    if (pi == null)
                    {
                        Type t = type;

                        while (t != null)
                        {
                            t = t.BaseType;
                            pi = GetSingleProperty(t, name, flags);
                            if (pi != null)
                                break;
                        }
                    }
                }
                catch (Exception ex)
                {
                    ex.LogInternal();
                }
                
                if (pi != null)
                {
                    if (!Properties.ContainsKey(type))
                        Properties.Add(type, new Dictionary<string, PropertyInfo>());
                    Properties[type][name] = pi;
                }
            }
            return pi;
        }
        /// <summary>
        /// Gets the single property.
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <param name="name">The name.</param>
        /// <param name="flags">The flags.</param>
        /// <returns></returns>
        public static PropertyInfo GetSingleProperty(this object obj, string name, BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy)
        {
            if (obj != null)
                return obj.GetType().GetSingleProperty(name, flags);
            return null;
        }

        /// <summary>
        /// Gets the property value.
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <param name="name">The name.</param>
        /// <returns></returns>
        public static object GetPropertyValue(this object obj, string name)
        {
            object result = null;
            TryGetPropertyValue(obj, name, out result);
            return result;
        }

        /// <summary>
        /// Tries to get property value.
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <param name="name">The name.</param>
        /// <param name="result">The result.</param>
        /// <returns></returns>
        public static bool TryGetPropertyValue(this object obj, string name, out object result)
        {
            bool res = false;
            result = null;
            if (obj != null)
            {
                try
                {
                    PropertyInfo pi = obj.GetType().GetSingleProperty(name, BindingFlags.Instance  | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy);
                    if (pi != null)
                    {
                        result = pi.GetValue(obj, null);
                        res = true;
                    }
                }
                catch { }
            }

            return res;
        }

        /// <summary>
        /// Sets the property value.
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <param name="name">The name.</param>
        /// <param name="value">The value.</param>
        public static void SetPropertyValue(this object obj, string name, object value)
        {
            obj.TrySetPropertyValue(name, value);
        }
        /// <summary>
        /// Tries to set property value.
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <param name="name">The name.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static bool TrySetPropertyValue(this object obj, string name, object value)
        {
            bool res = false;
            if (obj != null)
            {
                try
                {
                    PropertyInfo pi = obj.GetType().GetSingleProperty(name);
                    if (pi != null)
                    {
                        pi.SetValue(obj, value, null);
                        res = true;
                    }
                }
                catch { }
            }
            return res;
        }

        /// <summary>
        /// This should be used in Designers
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <param name="name">The name.</param>
        /// <param name="value">The value.</param>
        public static void SetPropertyValueWithPropertyDescriptor(this object obj, string name, object value)
        {
            if (obj != null)
            {
                try
                {
                    PropertyDescriptor prop = TypeDescriptor.GetProperties(obj)[name];
                    if (prop != null)
                        prop.SetValue(obj, value);
                }
                catch { }
            }
        }

        /// <summary>
        /// Gets the single field.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="name">The name.</param>
        /// <param name="flags">The flags.</param>
        /// <returns></returns>
        public static FieldInfo GetSingleField(this Type type, string name, BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy)
        {
            FieldInfo pi = null;
            if (type != null && name!=null)
            {
                if (Fields.ContainsKey(type) && Fields[type].ContainsKey(name))
                    return Fields[type][name];

                FieldInfo[] pis = type.GetFields(flags);
                try
                {
                    bool ignoreCase = ((flags & BindingFlags.IgnoreCase) == BindingFlags.IgnoreCase);
                    foreach (FieldInfo p in pis)
                    {
                        if ((p.Name == name) || (ignoreCase && (p.Name.ToUpper() == name.ToUpper())))
                        {
                            if (p.DeclaringType.Equals(type))
                            {
                                pi = p;
                                break;
                            }
                        }
                    }
                    if (pi == null)
                    {
                        Type t = type;

                        while (t != null)
                        {
                            t = t.BaseType;
                            pi = GetSingleField(t, name, flags);
                            if (pi != null)
                                break;
                        }
                    }
                }
                catch { }
                if (pi != null)
                {
                    if (!Fields.ContainsKey(type))
                        Fields.Add(type, new Dictionary<string, FieldInfo>());
                    Fields[type][name] = pi;
                }
            }
            return pi;
        }
        /// <summary>
        /// Gets the single field.
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <param name="name">The name.</param>
        /// <param name="flags">The flags.</param>
        /// <returns></returns>
        public static FieldInfo GetSingleField(this object obj, string name, BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy)
        {
            if (obj != null)
                return obj.GetType().GetSingleField(name, flags);
            return null;
        }

        /// <summary>
        /// Gets the field value.
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <param name="fieldName">Name of the field.</param>
        /// <returns></returns>
        public static object GetFieldValue(this object obj, string fieldName)
        {
            object result = null;
            obj.TryGetFieldValue(fieldName, out result);
            return result;
        }
        /// <summary>
        /// Tries to get field value.
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <param name="fieldName">Name of the field.</param>
        /// <param name="result">The result.</param>
        /// <returns></returns>
        public static bool TryGetFieldValue(this object obj, string fieldName, out object result)
        {
            bool res = false;
            result = null;
            if (obj != null)
            {
                try
                {
                    FieldInfo fi = obj.GetType().GetSingleField(fieldName);
                    if (fi != null)
                    {
                        result = fi.GetValue(obj);
                        res = true;
                    }
                }
                catch { }
            }
            return res;
        }

        /// <summary>
        /// Sets the field value.
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <param name="name">The name.</param>
        /// <param name="value">The value.</param>
        public static void SetFieldValue(this object obj, string name, object value)
        {
            obj.TrySetFieldValue(name, value);
        }
        /// <summary>
        /// Tries to set field value.
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <param name="name">The name.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static bool TrySetFieldValue(this object obj, string name, object value)
        {
            bool res = false;
            if (obj != null)
            {
                try
                {
                    FieldInfo pi = obj.GetType().GetSingleField(name);
                    if (pi != null)
                    {
                        pi.SetValue(obj, value);
                        res = true;
                    }
                }
                catch { }
            }
            return res;
        }
        
        #endregion

        /// <summary>
        /// Determines whether objects are the same.
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <param name="obj2">The obj2.</param>
        /// <returns></returns>
        public static bool IsTheSame(this object obj, object obj2)
        {
            if (obj.IsNull() && obj2.IsNull())
                return true;
            if (obj.IsNull() || obj2.IsNull())
                return false;
            if (obj.GetType() != obj2.GetType())
                return false;
            if(obj is IEnumerable && obj2 is IEnumerable)
            {
                IEnumerator en1 = (obj as IEnumerable).GetEnumerator();
                IEnumerator en2 = (obj2 as IEnumerable).GetEnumerator();
                while (en1.MoveNext())
                {
                    if (!en2.MoveNext())
                        return false;
                    if (!en2.Current.IsTheSame(en1.Current))
                        return false;
                }
                if (en2.MoveNext())
                    return false;
                return true;
            }
            return obj.Equals(obj2);
        }

        /// <summary>
        /// Casts the specified object to other type.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj">The object.</param>
        /// <param name="resultType">Type of the result.</param>
        /// <returns></returns>
        public static T Cast<T>(this object obj, T resultType)
        {
            T res = (T)obj;
            return res;
        }

        /// <summary>
        /// Gets the type.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj">The object.</param>
        /// <returns></returns>
        public static Type GetTypeSafe<T>(this T obj)
        {
            if (obj != null)
                return obj.GetType();
            return typeof(T);
        }

        /// <summary>
        /// Returns
        /// <para> - true if value is DefaultBool.True</para>
        /// <para> - false if value is DefaultBool.False</para>
        /// <para> - defaultValue in other cases</para>
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="defaultValue">if set to <c>true</c> [default value].</param>
        /// <returns></returns>
        public static bool AsBool(this DefaultBool value, bool defaultValue)
        {
            switch (value)
            {
                case DefaultBool.True:
                    return true;
                case DefaultBool.False:
                    return false;
                default:
                    return defaultValue;
            }
        }

        /// <summary>
        /// Sets destination (by reference) to specified value"
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value"></param>
        /// <param name="destination"></param>
        /// <returns></returns>
        public static bool AssignTo<T>(this T value, ref T destination)
        {
            if (!value.IsTheSame(destination))
            {
                destination = value;
                return true;
            }
            return false;
        }
    }
}
